/*
 * @Author: lei
 * @Description: IO复用之select示例
 * @FilePath: /io/select.cpp
 */

// complie: g++ -o select select.cpp -std=c++11
// run: ./select

#include <arpa/inet.h>   // inet_ntoa
#include <fcntl.h>       // fcntl
#include <netinet/in.h>  // socket地址类型
#include <string.h>      // bzero
#include <sys/select.h>  // select
#include <sys/socket.h>  // socket基础api
#include <sys/types.h>
#include <unistd.h>

#include <algorithm>
#include <iostream>
#include <string>
#include <unordered_set>
using namespace std;

const int N = 2048;  // 缓冲区大小；FIXME:vector<char>，动态扩容；ringbuf

#define exit_if(r, ...)                                            \
  if (r) {                                                         \
    printf(__VA_ARGS__);                                           \
    printf("error no: %d error msg %s\n", errno, strerror(errno)); \
    exit(1);                                                       \
  }

class EchoServer {
 public:
  // socket创建、bind、listen
  EchoServer(int ser_port) : ser_port_(ser_port) {
    struct sockaddr_in seraddr;
    bzero(&seraddr, sizeof seraddr);
    seraddr.sin_addr.s_addr = INADDR_ANY;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(ser_port);
    ser_fd_ = socket(AF_INET, SOCK_STREAM, 0);
    exit_if(ser_fd_ == -1, "socket() error");
    SetNonBlock(ser_fd_);
    SetReuse(ser_fd_);
    exit_if(::bind(ser_fd_, (const sockaddr*)&seraddr,
                   (socklen_t)sizeof seraddr) == -1,
            "bind() error");
    exit_if(listen(ser_fd_, 100) == -1, "listen() error");
    cout << "echo server listened on " << ser_port_ << endl;
  }
  ~EchoServer() {
    close(ser_fd_);
    for (auto& fd : connfds_) close(fd);
  }

  void Loop() {
    FD_SET(ser_fd_, &rfds_);
    // 备份
    rfds_bak_ = rfds_;
    wfds_bak_ = wfds_;

    struct timeval time, time_temp;
    time.tv_sec = 50;
    time.tv_usec = 0;
    time_temp = time;

    int ret, nready = 0, maxfd = ser_fd_;
    for (;;) {
      //每次循环之前的动作
      rfds_ = rfds_bak_;
      wfds_ = wfds_bak_;
      time_temp = time;

      nready = select(maxfd + 1, &rfds_, &wfds_, NULL, &time_temp);
      // cout << "nready= " << nready << ",maxfd= " << maxfd << endl;
      if (nready == 0) {  // select超时
        cout << time.tv_sec << "s内暂无事件发生" << endl;
        continue;
      } else if (nready == -1)  // select出错
        exit_if(1, "select() error");
      // select 正常返回，有fd有数据
      for (int j = 0; j <= maxfd; j++) {
        if (nready == 0) break;  // 处理完了提前返回
        // 有新连接请求
        if (j == ser_fd_ && FD_ISSET(j, &rfds_)) {
          ret = HandleConnection(ser_fd_, true);
          maxfd = max(ret, maxfd);
          nready--;
        }
        // connfd上有数据可读
        else if (FD_ISSET(j, &rfds_)) {
          HandleRead(j);
          nready--;
        }
        // connfd上有数据可写
        else if (FD_ISSET(j, &wfds_)) {
          // 如果wfds里有置位，则select不会被阻塞
          HandleWrite(j);
        }
      }
    }
  }

 private:
  void SetReuse(int fd) {
    int reuse = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, (socklen_t)sizeof reuse);
  }
  void SetNonBlock(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    exit_if(flags < 0, "fcntl failed");
    int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    exit_if(r < 0, "fcntl failed");
  }

  // 处理连接建立断开事件
  int HandleConnection(int fd, bool flag) {
    struct sockaddr_in cliaddr;
    socklen_t addrlen = sizeof cliaddr;
    bzero(&cliaddr, sizeof cliaddr);
    if (flag) {
      int connfd = accept(fd, (struct sockaddr*)&cliaddr, &addrlen);
      exit_if(connfd == -1, "accept() error");

      printf("accept a connection from %s:%d\n", inet_ntoa(cliaddr.sin_addr),
             cliaddr.sin_port);

      connfds_.insert(connfd);
      FD_SET(connfd, &rfds_bak_);
      return connfd;
    }
    cout << "客户端断开，fd= " << fd << endl;
    close(fd);
    FD_CLR(fd, &rfds_bak_);
    connfds_.erase(fd);
    return -1;
  }
  // 处理数据读事件
  int HandleRead(int fd) {
    bzero(buf_recv_, N);
    int len = recv(fd, buf_recv_, N, 0);
    if (len == 0)  // j上connection socket连接关闭
    {
      HandleConnection(fd, false);
      return 0;
    }
    exit_if(len == -1, "read()错误");
    cout << "read " << len << " bytes: " << buf_recv_
         << endl;  // telnet会把/r/n发过来；   netcat会把换行符一起发过来
    std::copy(buf_recv_, buf_recv_ + N, buf_send_);
    FD_SET(fd, &wfds_bak_);
    return len;
  }
  // 处理数据写事件
  int HandleWrite(int fd) {
    int len = send(fd, buf_send_, N, 0);
    exit_if(len == -1, "send() error");
    exit_if(len == 0, "server down");
    bzero(buf_send_, N);
    FD_CLR(fd, &wfds_bak_);
    return len;
  }

 private:
  char buf_send_[N];
  char buf_recv_[N];
  fd_set rfds_;
  fd_set wfds_;
  fd_set rfds_bak_;
  fd_set wfds_bak_;
  int ser_fd_;
  int ser_port_;
  unordered_set<int> connfds_;
};

int main() {
  EchoServer echo_server(10005);
  echo_server.Loop();
  return 0;
}
// FIXME:服务器主动断开，客户端没响应；最后一个FIN应当由客户端程序处理